/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
*/

#include <stdlib.h>

#include "ApplicationUsage.h"

namespace OCPP
{

    ApplicationUsage::ApplicationUsage(const String& commandLineUsage) :
        _commandLineUsage(commandLineUsage)
    {
    }

    std::shared_ptr<ApplicationUsage> ApplicationUsage::instance()
    {
        static std::shared_ptr<ApplicationUsage> s_applicationUsage(new ApplicationUsage);
        return s_applicationUsage;
    }

    void ApplicationUsage::addUsageExplanation(Type type, const String& option, const String& explanation)
    {
        switch (type)
        {
        case(COMMAND_LINE_OPTION):
            addCommandLineOption(option, explanation);
            break;
        case(ENVIRONMENTAL_VARIABLE):
            addEnvironmentalVariable(option, explanation);
            break;
        case(KEYBOARD_MOUSE_BINDING):
            //addKeyboardMouseBinding(option,explanation);
            break;
        default:
            break;
        }
    }

    void ApplicationUsage::addCommandLineOption(const String& option, const String& explanation, const String& defaultValue)
    {
        _commandLineOptions[option] = explanation;
        _commandLineOptionsDefaults[option] = defaultValue;
    }

    void ApplicationUsage::addEnvironmentalVariable(const String& option, const String& explanation, const String& defaultValue)
    {
        _environmentalVariables[option] = explanation;
        _environmentalVariablesDefaults[option] = defaultValue;
    }

    void ApplicationUsage::addKeyboardMouseBinding(const String& prefix, int key, const String& explanation)
    {
        if (key != 0)
        {
            std::ostringstream ostr;
            ostr << prefix;

            if (key == ' ')
            {
                ostr << "Space";
            }
            else if (key != 0)
            {
                ostr << char(key);
            }

            _keyboardMouse[ostr.str()] = explanation;
        }
    }


    void ApplicationUsage::addKeyboardMouseBinding(int key, const String& explanation)
    {
        addKeyboardMouseBinding("", key, explanation);
    }

    void ApplicationUsage::addKeyboardMouseBinding(const String& option, const String& explanation)
    {
        _keyboardMouse[option] = explanation;
    }

    void ApplicationUsage::getFormattedString(String& str, const UsageMap& um, unsigned int widthOfOutput, bool showDefaults, const UsageMap& ud)
    {

        unsigned int maxNumCharsInOptions = 0;
        ApplicationUsage::UsageMap::const_iterator citr;
        for (citr = um.begin();
            citr != um.end();
            ++citr)
        {
            maxNumCharsInOptions = std::max(maxNumCharsInOptions, (unsigned int)citr->first.length());
        }


        unsigned int fullWidth = widthOfOutput;
        unsigned int optionPos = 2;
        unsigned int explanationPos = optionPos + maxNumCharsInOptions + 2;

        double ratioOfExplanationToOutputWidth = float(explanationPos) / float(widthOfOutput);
        double maxRatioOfExplanationToOutputWidth = 0.25f;

        if (ratioOfExplanationToOutputWidth > maxRatioOfExplanationToOutputWidth)
        {
            explanationPos = static_cast<unsigned int>(maxRatioOfExplanationToOutputWidth * float(widthOfOutput));
        }

        unsigned int defaultPos = 0;
        if (showDefaults)
        {
            defaultPos = explanationPos;
            explanationPos = optionPos + 8;
        }
        unsigned int explanationWidth = fullWidth - explanationPos;

        String line;

        for (citr = um.begin();
            citr != um.end();
            ++citr)
        {
            line.assign(fullWidth, ' ');
            line.replace(optionPos, citr->first.length(), citr->first);
            unsigned int currentEndPos = optionPos + citr->first.length();

            if (showDefaults)
            {

                UsageMap::const_iterator ditr = ud.find(citr->first);
                if (ditr != ud.end())
                {
                    if (currentEndPos + 1 >= defaultPos)
                    {
                        str += line; str += "\n";
                        line.assign(fullWidth, ' ');
                    }

                    line.replace(defaultPos, String::npos, "");
                    if (ditr->second != "")
                    {
                        line += "[";
                        line += ditr->second;
                        line += "]";
                    }
                    str += line;
                    str += "\n";
                    line.assign(fullWidth, ' ');

                    currentEndPos = 0;
                }
            }

            const String& explanation = citr->second;
            String::size_type pos = 0;
            String::size_type offset = 0;
            bool firstInLine = true;
            if (!explanation.empty())
            {

                if (currentEndPos + 1 > explanationPos)
                {
                    str += line; str += "\n";
                    line.assign(fullWidth, ' ');
                }

                while (pos < explanation.length())
                {
                    if (firstInLine) offset = 0;

                    // skip any leading white space.
                    while (pos < explanation.length() && explanation[pos] == ' ')
                    {
                        if (firstInLine) ++offset;
                        ++pos;
                    }

                    firstInLine = false;

                    String::size_type width = std::min((String::size_type)(explanation.length() - pos), (String::size_type)(explanationWidth - offset));
                    String::size_type slashn_pos = explanation.find('\n', pos);

                    unsigned int extraSkip = 0;
                    bool concatinated = false;
                    if (slashn_pos != String::npos)
                    {
                        if (slashn_pos < pos + width)
                        {
                            width = slashn_pos - pos;
                            ++extraSkip;
                            firstInLine = true;
                        }
                        else if (slashn_pos == pos + width)
                        {
                            ++extraSkip;
                            firstInLine = true;
                        }
                    }

                    if (pos + width < explanation.length())
                    {
                        // now reduce width until we get a space or a return
                        // so that we ensure that whole words are printed.
                        while (width > 0 &&
                            explanation[pos + width] != ' ' &&
                            explanation[pos + width] != '\n') --width;

                        if (width == 0)
                        {
                            // word must be longer than a whole line so will need
                            // to concatenate it.
                            width = explanationWidth - 1;
                            concatinated = true;
                        }
                    }

                    line.replace(explanationPos + offset, explanationWidth, explanation, pos, width);

                    if (concatinated) { str += line; str += "-\n"; }
                    else { str += line; str += "\n"; }

                    // move to the next line of output.
                    line.assign(fullWidth, ' ');

                    pos += width + extraSkip;

                }
            }
            else
            {
                str += line; str += "\n";
            }
        }
    }

    void ApplicationUsage::write(std::ostream& output, const ApplicationUsage::UsageMap& um, unsigned int widthOfOutput, bool showDefaults, const ApplicationUsage::UsageMap& ud)
    {
        String str;
        getFormattedString(str, um, widthOfOutput, showDefaults, ud);
        output << str << std::endl;
    }

    void ApplicationUsage::write(std::ostream& output, unsigned int type, unsigned int widthOfOutput, bool showDefaults)
    {

        output << "Usage: " << getCommandLineUsage() << std::endl;
        bool needspace = false;
        if ((type & COMMAND_LINE_OPTION) && !getCommandLineOptions().empty())
        {
            output << "Options";
            if (showDefaults) output << " [and default value]";
            output << ":" << std::endl;
            write(output, getCommandLineOptions(), widthOfOutput, showDefaults, getCommandLineOptionsDefaults());
            needspace = true;
        }

        if ((type & ENVIRONMENTAL_VARIABLE) && !getEnvironmentalVariables().empty())
        {
            if (needspace) output << std::endl;
            output << "Environmental Variables";
            if (showDefaults) output << " [and default value]";
            output << ":" << std::endl;
            write(output, getEnvironmentalVariables(), widthOfOutput, showDefaults, getEnvironmentalVariablesDefaults());
            needspace = true;
        }

        if ((type & KEYBOARD_MOUSE_BINDING) && !getKeyboardMouseBindings().empty())
        {
            if (needspace) output << std::endl;
            output << "Keyboard and Mouse Bindings:" << std::endl;
            write(output, getKeyboardMouseBindings(), widthOfOutput);
            needspace = true;
        }

    }

    inline unsigned int getClampedLength(const char* str, unsigned int maxNumChars = 4096)
    {
        unsigned int i = 0;
        while (i < maxNumChars && str[i] != 0) { ++i; }
        return i;
    }

    inline String getEnvVar(const char* name)
    {
        String value;
        const char* ptr = getenv(name);
        if (ptr) value.assign(ptr, getClampedLength(ptr));
        return value;
    }

    template<typename T>
    inline bool getEnvVar(const char* name, T& value)
    {
        const char* ptr = getenv(name);
        if (!ptr) return false;

        std::istringstream str(String(ptr, getClampedLength(ptr)));
        str >> value;
        return !str.fail();
    }

    void ApplicationUsage::writeEnvironmentSettings(std::ostream& output)
    {
        output << "Current Environment Settings:" << std::endl;

        unsigned int maxNumCharsInOptions = 0;
        ApplicationUsage::UsageMap::const_iterator citr;
        for (citr = getEnvironmentalVariables().begin();
            citr != getEnvironmentalVariables().end();
            ++citr)
        {
            String::size_type len = citr->first.find_first_of("\n\r\t ");
            if (len == String::npos) len = citr->first.size();
            maxNumCharsInOptions = (std::max)(maxNumCharsInOptions, static_cast<unsigned int>(len));
        }

        unsigned int optionPos = 2;
        String line;

        for (citr = getEnvironmentalVariables().begin();
            citr != getEnvironmentalVariables().end();
            ++citr)
        {
            line.assign(optionPos + maxNumCharsInOptions + 2, ' ');
            String::size_type len = citr->first.find_first_of("\n\r\t ");
            if (len == String::npos) len = citr->first.size();
            line.replace(optionPos, len, citr->first.substr(0, len));

            String value;
            if (getEnvVar(citr->first.substr(0, len).c_str(), value))
            {
                line += "[set]\n";
            }
            else
            {
                line += "[not set]\n";
            }

            output << line;
        }
        output << std::endl;
    }
}

